import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:im_app/im/util/Md5Util.dart';
import 'package:logger/logger.dart';
import 'package:uuid/uuid.dart';

import '../binary/BufferUtil.dart';
import '../binary/ByteBuf.dart';
import '../socket/dto/base/Resp.dart';
import '../util/DigestUtils.dart';
import '../util/ImConstant.dart';
import 'ImSocket.dart';
import 'UploadAndDownloadDto.dart';

const fileDownloadPSize = ImConstant.FILE_UPLOAD_P_SIZE;
const String TAG = 'FileDownloadSocket';

typedef DownloadCallback = void Function(FileDownloadResult fileDownloadResult);

class FileDownloadSocket extends ImSocket {

  final _logger = Logger(printer: PrettyPrinter(),);

  String tenantCode;
  String userId;
  int messageId;
  String messageServerId;
  String fileOriginName;
  int fileSize;
  String md5;
  String fileUrl;
  String cacheDir;
  String userDir;
  String ip;
  int port;

  ErrorCallback errorCallback = (ErrorResult errorResult)=>{};
  ReceiveData receiveData = (List<int> data)=> {}; // 接受数据回调

  DownloadCallback downloadListener = (FileDownloadResult uploadResult)=>{};

  ByteBuf byteBuf = ByteBuf(fileDownloadPSize);
  int downloadSize = 0;
  bool isFileDownloadResponse = false;
  int downloadFileStartTime = 0;

  IOSink? fileStream = null;
  String cacheFilePath = '';
  int receiveDataTime = 0;
  int receiveDataSize = 0;
  int receiveDataStartTime = 0;

  Completer<Object> completer = Completer();

  FileDownloadSocket(this.tenantCode, this.userId, this.messageId, this.messageServerId, this.fileOriginName,
    this.fileSize, this.md5, this.fileUrl, this.cacheDir, this.userDir, this.ip, this.port) : super(ip: ip, port: port) {
    initData(receiveSocketData, socketErrorCallback);
  }

  setDownloadListener(DownloadCallback downloadListener) {
    this.downloadListener = downloadListener;
  }

  // 发送下载文件信息
  Object sendFileDownloadReq(int timeout) async  {
    var req = FileDownloadReq();
    req.tenantCode = tenantCode;
    req.userId = userId;
    req.fileServerId = messageServerId;
    req.fileUrl = fileUrl;
    req.localDir = cacheDir;

    var content = "${req.fileUrl}_${req.userId}_${req.fileServerId}";
    var authCode = DigestUtils.sha1(content);
    req.fileAuthCode = authCode;

    var byteBuf = new ByteBuf(1024);
    byteBuf.writeByte(20);  // 文件下载
    byteBuf.writeInt(0);
    BufferUtil.encode2(byteBuf, req);
    var length = byteBuf.writeOffset - 5;
    byteBuf.setInt(1, length);

    await sendData(byteBuf.getArrayBuffer());
    _logger.i("$TAG, [Download send file req], 发送下载文件信息");

    return completer.future.timeout(Duration(seconds: timeout));
  }

  void receiveSocketData(List<int> data) async {
    _logger.i("$TAG, [Download receive data], start: ${data.length}");
    try {
      if (isFileDownloadResponse == false) {
        receiveFileDownloadResponse(data);

        // 还有剩余数据
        var remainLength = byteBuf.writeOffset - byteBuf.readOffset;
        if (remainLength > 0) {
          await receiveFileData(byteBuf.slice(remainLength).toList());
        }
        byteBuf.shake();
      } else {
        await receiveFileData(data);
      }
    } catch(error, stackTrace) {
      _logger.e("$TAG, [Download receive data error]", error: error, stackTrace: stackTrace);
    } finally {
      _logger.i("$TAG, [Download receive data], end: ${data.length}");
    }
  }

  // 发送文件下载请求后，接受返回的结果数据
  receiveFileDownloadResponse(List<int> data) {
    _logger.i("$TAG, [Download receive data], receiveFileDownloadResponse: ${data.length}");
    var arrayBuf = Uint8List.fromList(data);
    byteBuf.writeArrayBuffer(arrayBuf);

    byteBuf.markReadIndex();
    var length = byteBuf.readInt()!;
    var readableSize = byteBuf.readableSize();
    if (readableSize < length + 1) {
      byteBuf.resetMarkReadIndex();
      return;
    }
    var status = byteBuf.readByte();
    if (status == -1) { // 异常数据
      var exceptionResp = ExceptionResp(-1, '');
      BufferUtil.decode(exceptionResp, byteBuf);
      _logger.e("$TAG, [Download data error], ${exceptionResp.errorStatus}/${exceptionResp.errorMsg}");
      completer.complete(exceptionResp);
      return;
    }
    var fileDownloadInfo = FileDownloadInfo();
    BufferUtil.decode(fileDownloadInfo, byteBuf);

    isFileDownloadResponse = true;

    _logger.i("$TAG, [Download receive data], receiveFileDownloadResponse success");
    completer.complete(fileDownloadInfo);
  }

  // 接受文件数据
  receiveFileData(List<int> data) async {
    try {
      if (fileStream == null) {
        var uuid = const Uuid().v1();
        cacheFilePath = cacheDir + uuid + ".tmp";
        _logger.i("$TAG, [Download create tmp file], $cacheFilePath");
        // let file = fileIo.openSync(this.cacheFilePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
        fileStream = File(cacheFilePath).openWrite();
        receiveDataStartTime = DateTime.now().millisecond;
      }

      _logger.i("$TAG, [Download receive data size], $receiveDataSize:${data.length }:$fileSize");
      // this.cacheFile.writeSync(data.message.slice(0, data.message.byteLength))
      fileStream!.add(data);

      receiveDataSize += data.length;
      _logger.i("$TAG, [Download receive data size and write to file], $receiveDataSize:${data.length}:$fileSize");
      var nowTime = DateTime.now().millisecond;
      if (nowTime - receiveDataTime > 400 && fileSize != receiveDataSize) {
        _logger.i("$TAG, [Download receive data size and update ui], $receiveDataSize:${data.length}:$fileSize");
        downloadListener(FileDownloadResult(fileSize, receiveDataSize, false, ''));
        receiveDataTime = nowTime;
      }

      if (fileSize == receiveDataSize) { // 文件下载成功
        var time = ((DateTime.now().millisecond - receiveDataStartTime)/1000).toStringAsFixed(2);
        _logger.i("$TAG, [Download receive data size end], $receiveDataSize/$fileSize/time: $time");
        await fileStream!.close();
        fileStream = null;
        var md5 = fileSha512(cacheFilePath);
        md5 = md5.toLowerCase();
        var destFile = userDir + fileOriginName;
        if (md5 == this.md5) {
          await moveTmpFile(cacheFilePath, destFile);
          downloadListener(FileDownloadResult(fileSize, receiveDataSize, true, ''));
        } else {
          _logger.e("$TAG, [Download md5 error], 文件验签失败, $md5, $this.md5");
          downloadListener(FileDownloadResult(fileSize, receiveDataSize, false, '文件验签失败'));
        }
      }
    } catch(error, stackTrace) {
      _logger.e("$TAG, [Download receive data error]", error: error, stackTrace: stackTrace);
      if (fileStream != null) {
        await fileStream!.close();
      }
      downloadListener(FileDownloadResult(fileSize, receiveDataSize, false, '接受数据失败'));
    }
  }

  moveTmpFile(String srcFile, String destFile) async {
    var file = File(srcFile);
    while(true) {
      try {
        await file.rename(destFile);
        _logger.i("$TAG, [Download move success]");
        break;
      } catch (error, stackTrace) {
        _logger.e("$TAG, [Download move err]", error: error, stackTrace: stackTrace);
      }
      await Future.delayed(const Duration(seconds: 1));
    }
  }

  socketErrorCallback(ErrorResult errorResult) {
    downloadListener(FileDownloadResult(fileSize, downloadSize, false, errorResult.errorMsg));
  }
}